<?php
// +----------------------------------------------------------------------
// | 杭州十禾信息科技有限公司
// +----------------------------------------------------------------------
// | Copyright (c) 2013-2015 http://www.shelpe.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: LeePuman <ysyzlsm@163.com>
// +----------------------------------------------------------------------

class QuotaLimitException extends ThinkException{
}

class ApiModel extends NewzanModel {
	
	protected $tableName = 'api_log';
	
	private $me = null;
	private $topClient = null;
	private $_useSessOnly = false;
	
	
	const MAX_WORDS_BASIC_PROPS_PER_REQUEST = 100;

	public function __construct($useSessOnly = false) {
		$this->_useSessOnly = $useSessOnly;
		parent::__construct();
	}

	public function _initialize() {
		parent::_initialize();
		$this->me = session('me');
	}
	
	public function request($topClassName, $params = null, $userId = 0) {
        //控制基于clientAction的__ACTION__的流量
        self::checkClientApiQuota();
        //slow down the api request
		//$access_token = $this->me['oauth']['access_token']; //getUserAccessToken($this->me['user_id']);
		$oauth = null;
		$me = null;
		if(session('?manager')) {
			$manager = session('manager');
			$oauth = $manager['oauth'];
			$me = session('me');
		} else if(session('?me')) {
			$me = session('me');
			$oauth = $me['oauth'];
		} else if(!$this->_useSessOnly) {
			$userMdl = new UserModel();
			if( $userId ){
				$me = $userMdl->getUserByTaobaoId($userId);
				if(!$me) {
					throw_exception('给定的用户不存在');
				}
				if($me['authorized_to'] == 0) {
					$oauth = D('oauth')->where(array('taobao_user_id' => $userId))->find();
				} else {
					$oauth = D('oauth')->where(array('taobao_user_id' => $me['authorized_to']))->find();
				}				
			}else{
				$proxy_user = $userMdl->getAvailableUserForApi();
				$me = $proxy_user['user'];
				$oauth = $proxy_user['oauth'];				
			}

		}
		
		if(!$me) 
			throw_exception('ApiModel::request()没有给定用户');
		if(!$oauth) 
			throw_exception('ApiModel::request()无法获取oauth权限');
		
		$access_token = $oauth['access_token'];
		
		$c = $this->getTopClient();
		import('@.Library.TaobaoAPI.TopClient');
		$req = TopClient::init($topClassName);
		foreach($params as $key => $val) {
			$setMethod = 'set' . ucfirst($key);
			$req->$setMethod( (string) $val);
		}
		if((!isset($params['nick']) && !isset($params['Nick']))) {
			$req->putOtherTextParam('nick', $me['nick']); //UserModel::getSelfOrAuthUserNick()
		}
		return $c->execute($req, $access_token);
	}
	
	public function getTopCates() {
		return $this->request('SimbaInsightToplevelcatsGetRequest');
	}
	
	public function getAllCates() {
		$succ = false;
		$taskId = 77231652;
		if($taskId) {
			$maxTry = 1;
			$try = 0;
			while(++$try <= $maxTry) {
				$resp = $this->request('TopatsResultGetRequest', array('TaskId' => $taskId));
				if($resp->task->status == 'done') {
					$succ = true;
					$url = $resp->task->download_url;
					download_file('/taobao_all_cates.zip', $url);
					break;
				} else {
					sleep(300);
				}
			}
		}
	}
	
	public function getCateHotWords($cateIds, $maxWordsNum = 100) {
		$cates = is_array($cateIds) ? join(',', $cateIds) : @$cateIds;
		if(empty($cates)) 
			return null;
	
		$resp = $this->request('SimbaInsightCatstopwordGetRequest', array(
				'CategoryIds' => $cates,
				'ResultNum' => $maxWordsNum,
		));
		return $resp->top_words->string ? zh_arrtolower($resp->top_words->string) : array();
	}
	
	public function getWordProps($words, $timeSpan = 'WEEK', $filter = 'PV,CLICK,AVGCPC,COMPETITION,CTR') {
		if(is_array($words))
			$words = join(',', $words);
		if(empty($words))
			return null;
		try {
			$resp = $this->request('SimbaInsightWordsbaseGetRequest', array(
					'Words' => $words,
					'Time' => $timeSpan,
					'Filter' => $filter,
			));
		} catch (Exception $e) {
			if(getTopError($e->getMessage() == '字符串解析出错'))
				Log::record('ApiModel::getWordProps() error words: ' . $words . '.', Log::ERR, true);
			throw $e;
		}
		
		//正常返回
		if( isset($resp->in_word_bases)  ){
			if( isset( $resp->in_word_bases->i_n_word_base ) ){
				//有数据的词
				return $resp->in_word_bases->i_n_word_base;
			}else{
				//正常返回但是所有词语全部没有数据，确定为垃圾词
				return array();
			}
		//非正常返回
		}else{
			return null;
		}
	}
	
	public function getWordsData($words, $start_date = null , $end_date = null) {
		if(is_array($words)){
			$words = join(',', $words);
		}		
		
		if(empty($words)){
			return null;
		}
		
		if( count($words) > 100 ){
			throw new NewzanException("请求词语量过大，一次只能请求100个");
		}
		
		try {
			$resp = $this->request('SimbaInsightWordsdataGetRequest', array(
					'bidwordList' => $words,
					'startDate' => $start_date,
					'endDate' => $end_date,
			));
		} catch (Exception $e) {
			if(getTopError($e->getMessage() == '字符串解析出错'))
				Log::record('ApiModel::getWordProps() error words: ' . $words . '.', Log::ERR, true);
			throw $e;
		}
	
		//正常返回
		
		if( isset($resp->word_data_list)  ){
			if( isset($resp->word_data_list->insight_word_data_d_t_o) ){
				return $resp->word_data_list->insight_word_data_d_t_o;
			}else{
				return array();
			}
		}else{
			return null;
		}
		
	}
	
	public function getBigWordProps($words, $timeSpan = 'WEEK', $fields = 'PV,CLICK,AVGCPC,COMPETITION,CTR', $blockSize = 30) {
		if(empty($words))
			return null;
		
		$i = 0; 
		$n = count($words);
		$res = array();
		while($i * $blockSize < $n) {
			$blockWords = array_slice($words, $i * $blockSize, $blockSize); // TODO try catch exception
			$res += $this->getWordProps($blockWords, $timeSpan, $fields);
			++$i;
		}
		return $res;
	}

	public function getWordAnalysis($words, $field) {
		if(is_array($words))
			$words =join(',', $words);
		if(empty($words))
			return null;

		$resp = $this->request('SimbaInsightWordsanalysisGetRequest', array(
				'Words' => $words,
				'Stu' => $field,
		));
		
		//如果返回正常应该有此对象属性
		if( isset($resp->in_word_analyses) ){
			if( isset( $resp->in_word_analyses->i_n_word_analysis ) ){
				return $resp->in_word_analyses->i_n_word_analysis;
			}else{
				return array();
			}
		}else{
		//返回异常
			return null;
		}
	}
	
	public function getWordAllAnalysis($words) {
		$res = array();
		$res['area'] = $this->getWordAnalysis($words, 'AREA');
		$res['source'] = $this->getWordAnalysis($words, 'SOURCE');
		$res['hprice'] = $this->getWordAnalysis($words, 'HPRICE');
		return $res;   
	}
	
	public function getRelatedWords($words, $resNum = 10) {
		
		if(is_array($words))
			$words =join(',', $words);
		if(empty($words))
			return null;
		
		$resp = $this->request('SimbaInsightRelatedwordsGetRequest' , array(
				'bidwordList' => $words,
				'number' => $resNum
		));
		
		$words = $resp->related_words_result_list->insight_related_words;
		return $words ? $words : array();
		//return $resp->related_words_result_list->insight_related_words ? zh_arrtolower($resp->related_words->string) : array();
	}
	
	public function getSearchRealtedWords($word, $maxTry = 8) {
		if($maxTry > 10) {
			throw new NewzanException('最大尝试次数不能超过10.',RetStatus::PARAM_ERR);
		}
		import('@.Library.Carine');
		$curl = new Carine();
		$sleepTime = array(2, 5, 10, 60, 120, 300, 600, 1800, 3600); // 失败n次后，进程暂停的秒数。
		$url = RELATED_SEARCH_URL_BASE . urlencode($word);
		$relatedWords = null;
		for($try = 0; $try < $maxTry; ++$try) {
			$failed = true;
			// $reqRes = file_get_contents($url);
			$reqRes = $curl->getTaobaoSearchReleatedWords($word);
			if($reqRes !== false) {
				$relatedWords = json_decode($reqRes);
				if($relatedWords !== null)
					$failed =false; 
			} 
			if($failed) {
				if($try == $maxTry) {
					return false;
				}
				sleep($sleepTime[$try]);
			} else {
				break;
			}
		}
		
		// 抽出返回的结果中的关联词。
		$res = array();
		foreach($relatedWords->result as $rWord) {
			//$res[ $rWord[1] ] = $rWord[0];
			$res[] = zh_strtolower($rWord[0]);
		}

		return $res;
	}

	public function addWords($keywordPrices, $adgroupId) {
		require_once LIB_PATH . "Library/TaobaoAPI/Curl.php";
		if(empty($keywordPrices))
			return array();
		
		$result = array();
		
		$filterdKeywordPrices = array();
		$mobileKeywordPrices = array();
		foreach($keywordPrices as $k){
			if($k['default_mobile_price'] == 1) {
				$mobileKeywordPrices['names'][] = $k['word'];
				$mobileKeywordPrices['info'][$k['word']] = $k;
			}
			$filterdKeywordPrices[] = array('word' => $k['word'], 'maxPrice' => round($k['max_price']), 'matchScope' => $k['matchscope']);
		}

		$curl = new Curl();
        $url = build_url("sinbadservice/users/{$this->me['user_id']}/adgroups/{$adgroupId}/keywords");
        $resp = $curl->setOpt(CURLOPT_HTTPHEADER, array('Content-Type: text/plain'));
        $resp = $curl->post($url, json_encode($filterdKeywordPrices));
		if($curl->httpStatusCode == 200) {
			$result = object_to_array($resp->simba_keywordsvon_add_response->keywords->keyword);
			$updateKeyword = array();
			foreach ($result as $k) {
				$keyword_id = (string)$k['keyword_id'];
				if(in_array($k['word'], $mobileKeywordPrices['names'])) {
					$updateKeyword[$keyword_id] = array(
							'keyword_id' => $k['keyword_id'],
							'max_price' => $k['max_price'],
							'matchscope' => $k['match_scope']
						);

					if($mobileKeywordPrices['info'][$k['word']]['default_mobile_price'] === 1) {
						$updateKeyword[$keyword_id]['mobile_is_default_price'] = 0;
						$updateKeyword[$keyword_id]['max_mobile_price'] = $mobileKeywordPrices['info'][$k['word']]['mobile_price'];
					}
				}
			}
			$this->setPrices($updateKeyword, $adgroupId);

		} else {
			throw new Exception("服务器错误：{$resp->message}", 1);
		}
    	
		return $result;
	}
	
	public function setPrices($keywordPrices, $adgroupId = 0) {
		require_once LIB_PATH . "Library/TaobaoAPI/Curl.php";
		$filterdKeywordPrices = array();
		foreach($keywordPrices as $key => $keyword){
			$keyword_id = (string)$key;
			//$filterdKeywordPrices[] = $k['keyword_id'] . '^^' . $k['max_price'] . '^^' . $k['matchscope'];
			$filterdKeywordPrices[$keyword_id] = array(
					'keywordId' => $keyword['keyword_id'],
					'maxPrice' => $keyword['max_price'],
					'matchScope' => $keyword['matchscope']
			);

			if(isset($keyword['mobile_is_default_price']) && (int)$keyword['mobile_is_default_price'] === 0) {
				$filterdKeywordPrices[$keyword_id]['mobileIsDefaultPrice'] = 0;
				$filterdKeywordPrices[$keyword_id]['maxMobilePrice'] = $keyword['max_mobile_price'];
			} 
		}
		$filterdKeywordPrices = array_values($filterdKeywordPrices);
		
        $curl = new Curl();
        $url = build_url("sinbadservice/users/{$this->me['user_id']}/adgroups/{$adgroupId}/keywords/price");
        $resp = $curl->setOpt(CURLOPT_HTTPHEADER, array('Content-Type: text/plain'));
        $resp = $curl->post($url, json_encode($filterdKeywordPrices));
        if($curl->httpStatusCode != 200)
            return array('code' => 'error', 'msg' => '服务器错误', 'detail' => $res->message);
        try {
        	$resp = json_decode($resp);
        	$resp = $resp->simba_keywords_pricevon_set_response;

        	return array('code' => 'success' , 'result'=> object_to_array($resp->keywords->keyword));
        } catch (Exception $e) {
        	
        }
		// $resp = $this->request('SimbaKeywordsPricevonSetRequest', $params);
	}

	public function deleteWordsSmall($keywordIds, $campaignId) {
		if(!$keywordIds)
			return array();
		$keywordIds = is_array($keywordIds) ? join(',', $keywordIds) : $keywordIds;

		$resp = $this->request('SimbaKeywordsDeleteRequest', array(
				'CampaignId' => $campaignId,
				'KeywordIds' => $keywordIds,
		));
		
		return object_to_array($resp->keywords->keyword);
	}

	public function deleteWords($keywordsIds, $campaignId) {
		if(!$keywordsIds)
			return array();
		if($campaignId <= 0) 
			throw_exception('参数错误.');
		
		$sliceSize = 100;
		if((is_array($keywordsIds) && count($keywordsIds) <= $sliceSize) 
				|| (!is_array($keywordsIds) && substr_count($keywordsIds, ',') < $sliceSize) ) {
			return $this->deleteWordsSmall($keywordsIds, $campaignId);
		} else {
			$result = array();
			 if(!is_array($keywordsIds)) {
			 	$keywordsIds = explode(',', $keywordsIds);
			 }
			 	
		 	$sliceNum = ceil(count($keywordsIds) / $sliceSize);
		 	for($i = 0; $i < $sliceNum; ++$i) {
		 		$pageKeywordIds = array_slice($keywordsIds, $i * $sliceSize, $sliceSize);
		 		$pageResult = array();
		 		try {
		 			$pageResult = $this->deleteWordsSmall($pageKeywordIds, $campaignId);
		 		} catch(TopClientException $e) {
		 			if(empty($result)) { //如果还没有成功删除任何词, 可以将异常直接抛出
		 				throw $e;
		 			} else { // 如果已经成功删除一些词, 需要将这些词返回.
			 			Log::record('批量删除关键词出错. ' . $e->getMessage(), Log::ERR);
			 			Log::record('Trace: ' . $e->getTraceAsString(), Log::ERR);
		 			}
		 		}
		 		$result = array_merge($result, $pageResult);
		 	}
			return $result;
		}
	}

	public function getCateWordsStats($words, $cateId , $from = null , $to = null) {
		if(empty($words) || empty($cateId) || $cateId <= 0) {
			throw_exception('Parameter error at function getCateWordsStats(). Given parameters are $word: '
					. join(',', $words), ' and $cateId: ' . $cateId);
		}
		if(count($words) > 200) {
			throw_exception('parameter $words cannot have more than 200 elements');
		}
		$wordCategories = '';
		foreach($words as $word) {
			$word = trim($word);
			if(!empty($word)) 
				$wordCategories .= ",$word^^$cateId";
		}
		if(empty($wordCategories)) 
			return array();
		
		$wordCategories = substr($wordCategories, 1);
		$fields = 'PV,CLICK,AVGCPC,COMPETITION,CTR';
		
		//时间范围
		$startDate = $startDate ? $startDate : date2time(-7);
		$endDate = $endDate ? $endDate : date2time(-1);
		
		/**
		 * 升级API
		 */
		$resp = $this->request('SimbaInsightCatsworddataGetRequest' , array(
				'catId' => $cateId,
				'bidwordList' => join(',',$words),
				'startDate' => $from ,  
				'endDate' => $to
		));
		
		if(empty($resp) || empty($resp->catword_data_list))
			return array();
		else {
			$words = isset($resp->catword_data_list->insight_word_data_under_cat_d_t_o) ?
			$resp->catword_data_list->insight_word_data_under_cat_d_t_o : null;
			return $words ? $words : array();
		}
		
		/*
		$resp = $this->request('SimbaInsightWordscatsGetRequest', array(
				'WordCategories' => $wordCategories,
				'Filter' => $fields
		));
		if(empty($resp) || empty($resp->in_word_categories))
			return array();
		else {
			$words = isset($resp->in_word_categories->i_n_word_category) ?
				$resp->in_word_categories->i_n_word_category : null;
			return $words ? $words : array();
		}*/
	}

	public function getAdgroupRcmm($userId, $adgroupId, $pageNo = 0, $pageSize = 200) {
		$cacheKey = 'adgroup_rcmm_' . $adgroupId;
		$allRcmmWords = S($cacheKey);
		if(false === $allRcmmWords) {
			$resp = $this->request(
					'SimbaKeywordsRecommendGetRequest', 
					array(
							'AdgroupId' => $adgroupId,
							'PageSize' => $pageSize, //返回的每页数据量大小,最大200
							'PageNo' => ($pageNo > 0? $pageNo : 1), //返回的第几页数据，默认为1
							'OrderBy' => 'relevance', //排序方式: 搜索量 search_volume 市场平均价格 average_price 相关度 relevance 不排序 non 默认为 non
							// 'pertinence' => 0, //相关度
							// 'search' => 0, //搜索量,设置此值后返回的就是大于此搜索量的词列表
					),
					$userId
			);
			$allRcmmWords = $resp->recommend_words->recommend_word_list->recommend_word;
			zh_arr2dtolower($allRcmmWords, 'word', true);
			
			if($pageNo == 0) {
				$pageNo = 1;
				$totalNum = $resp->recommend_words->total_item;
				while(count($allRcmmWords) < $totalNum && $pageNo < 10) {
					$resp = $this->request(
							'SimbaKeywordsRecommendGetRequest', 
							array(
									'AdgroupId' => $adgroupId,
									'PageSize' => $pageSize,
									'PageNo' => ++$pageNo,
									'OrderBy' => 'relevance',
							),
							$userId
					);
					if(!empty($resp->recommend_words->recommend_word_list->recommend_word)) {
						$subRcmmWords = $resp->recommend_words->recommend_word_list->recommend_word;
						zh_arr2dtolower($subRcmmWords, 'word', true);;
						$allRcmmWords = array_merge($allRcmmWords, $subRcmmWords);
					}
				}
				S($cacheKey, $allRcmmWords, 7200);
			}
			
		} else if($pageNo > 0) {
			$allRcmmWords = array_slice($allRcmmWords, ($pageNo - 1) * $pageSize, $pageSize);
		}
		return $allRcmmWords;
	}
	
	protected function checkClientApiQuota($num = 1){
		try{
        	$cache = Cache::getInstance('memcache');
		}catch(Exception $e){
			return;
		}
        if(! $cache->isConnected() ){
           // throw new NewzanException('memcache缓存未开启');          
           return 100;
        }
	    $quota = $cache->get("TOP_API_QUOTA");
	    //没有初始化计数器的时候重新初始化
	    if(!$quota) self::resetApiQuota();
        $quota = intval($quota);
	    if( $quota < 1 && defined(CLI_MODE) ) {
	        throw new QuotaLimitException("今天客户端更新api限额到了");
	    }
	    $quota -= $num;
        $cache->set("TOP_API_QUOTA" , $quota);
        return $quota;  
	}
	
    public static function resetApiQuota(){
        $cache = Cache::getInstance('memcache');
        $apiMdl = new ApiModel();
        $quota = $apiMdl->getAvaiableQuota();
        $cache->set("TOP_API_QUOTA" , $quota);    
    }

	public function getUsedQuota() {
		// 先查出今天第一次API请求的ID, 然后根据ID来查询今天使用的API量, 以提升效率
		$sKey = 'firstApi' . date('d');
		$todays1stApiId = S($sKey);
		if(!$todays1stApiId) {
			$todays1stApiId = $this->where(array('req_time' => array('egt', date('Y-m-d'))))->getField('id');
			if($todays1stApiId) {
				S($sKey, $todays1stApiId, 86400);
			}
		}
		if($todays1stApiId) {
			// $localUsed = $this->where("id>=$todays1stApiId")->count(); // TODO 使用 max(id) - $todays1stApiId 或者 (select id order by id desc limit 1) -$todays1stApiId 是否更快?
			$currMaxId = $this->order('id desc')->getField('max(id)');
		}
		
		return $currMaxId - $todays1stApiId;
	}

	public function getAvaiableQuota($max = DAILY_API_QUOTA, $reserved = DAILY_API_QUOTA_RESERVED) {
		$used = $this->getUsedQuota();
		return $max - $used - $reserved;
	}
	
	private function getTopClient() {
		if($this->topClient === null) {
			import('@.Library.TaobaoAPI.TopClient');
			$this->topClient = new TopClient;
		}
		return $this->topClient;
	}
}
